package member

import (
	"encoding/json"
	"fmt"

	"phant-operator/pkg/apis/label"

	apps "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	apiequality "k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/util/strategicpatch"
	"k8s.io/klog/v2"
)

const (
	// LastAppliedConfigAnnotation is annotation key of last applied configuration
	LastAppliedConfigAnnotation = "phant.io/last-applied-configuration"
	// ImagePullBackOff is the pod state of image pull failed
	ImagePullBackOff = "ImagePullBackOff"
	// ErrImagePull is the pod state of image pull failed
	ErrImagePull = "ErrImagePull"
)

// templateEqual compares the new podTemplateSpec's spec with old podTemplateSpec's last applied config
func templateEqual(new *apps.StatefulSet, old *apps.StatefulSet) bool {
	oldStsSpec := apps.StatefulSetSpec{}
	lastAppliedConfig, ok := old.Annotations[LastAppliedConfigAnnotation]
	if ok {
		err := json.Unmarshal([]byte(lastAppliedConfig), &oldStsSpec)
		if err != nil {
			klog.Errorf("unmarshal PodTemplate: [%s/%s]'s applied config failed,error: %v", old.GetNamespace(), old.GetName(), err)
			return false
		}
		return apiequality.Semantic.DeepEqual(oldStsSpec.Template.Spec, new.Spec.Template.Spec)
	}
	return false
}

func annotationsMountVolume() (corev1.VolumeMount, corev1.Volume) {
	m := corev1.VolumeMount{Name: "annotations", ReadOnly: true, MountPath: "/etc/podinfo"}
	v := corev1.Volume{
		Name: "annotations",
		VolumeSource: corev1.VolumeSource{
			DownwardAPI: &corev1.DownwardAPIVolumeSource{
				Items: []corev1.DownwardAPIVolumeFile{
					{
						Path:     "annotations",
						FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.annotations"},
					},
				},
			},
		},
	}
	return m, v
}

// getStsAnnotations gets annotations for statefulset of given component.
func getStsAnnotations(tcAnns map[string]string, component string) map[string]string {
	anns := map[string]string{}
	if tcAnns == nil {
		return anns
	}

	// ensure the delete-slots annotation
	var key string
	switch component {
	case label.PatroniLabelVal:
		key = label.AnnParoniDeleteSlots
	default:
		return anns
	}
	if val, ok := tcAnns[key]; ok {
		anns[label.DeleteSlotsAnn] = val
	}

	return anns
}

// MergePatchContainers adds patches to base using a strategic merge patch and
// iterating by container name, failing on the first error
func MergePatchContainers(base, patches []corev1.Container) ([]corev1.Container, error) {
	var out []corev1.Container

	containersByName := make(map[string]corev1.Container)
	for _, c := range patches {
		containersByName[c.Name] = c
	}

	// Patch the containers that exist in base.
	for _, container := range base {
		patchContainer, ok := containersByName[container.Name]
		if !ok {
			// This container didn't need to be patched.
			out = append(out, container)
			continue
		}

		containerBytes, err := json.Marshal(container)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal JSON for container %s, error: %v", container.Name, err)
		}

		patchBytes, err := json.Marshal(patchContainer)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal JSON for patch container %s, error: %v", container.Name, err)
		}

		// Calculate the patch result.
		jsonResult, err := strategicpatch.StrategicMergePatch(containerBytes, patchBytes, corev1.Container{})
		if err != nil {
			return nil, fmt.Errorf("failed to generate merge patch for container %s, error: %v", container.Name, err)
		}

		var patchResult corev1.Container
		if err := json.Unmarshal(jsonResult, &patchResult); err != nil {
			return nil, fmt.Errorf("failed to unmarshal merged container %s, error: %v", container.Name, err)
		}

		// Add the patch result and remove the corresponding key from the to do list.
		out = append(out, patchResult)
		delete(containersByName, container.Name)
	}

	// Append containers that are left in containersByName.
	// Iterate over patches to preserve the order.
	for _, c := range patches {
		if container, found := containersByName[c.Name]; found {
			out = append(out, container)
		}
	}

	return out, nil
}
